Microservices is a variant of the service-oriented architecture (“SOA”) architectural style that structures an application as a collection of loosely coupled services. The central idea behind microservices is that some types of applications become easier to build and maintain when they are broken down into smaller, composable pieces which work together. Each component is continuously developed and separately maintained, and the application is composed of the sum of its constituent components. In contrast to a traditional “monolithic” application, which is developed as one integrated unit, microservices applications are built as a set of modular components, easier to understand, easier to test, and easier to maintain over the life of the application.
In more detail, in a microservices architecture, services are typically fine-grained utilizing lightweight protocols. Each service is elastic, resilient, composable, minimal, and complete. Microservice design principles embrace failure and faults, similar to anti-fragile systems. Applications built using the microservice architecture tend to use lightweight, open-source technologies. The services communicate via so-called ‘dumb pipes’, such as a message broker or lightweight protocols such as RESTful (“Representational State Transfer”) or RPC (“Remote Procedure Call”). The benefit of decomposing an application into different smaller services (microservices) is that it improves modularity and makes the application easier to understand, develop and test. It also parallelizes development by enabling small autonomous teams to develop, deploy and scale their respective services independently of other teams. Further, it allows the architecture of an individual service to emerge through continuous refactoring whereby different development teams work on independently deployable units of code. To this end, microservices-based architectures enable continuous delivery and deployment so that organizations can achieve higher agility and reduce the time it takes to achieve working improvements to productivity.
A characteristic of the microservice architecture is that the services are loosely coupled. One way to achieve loose coupling is by each service having its own data store and associated data application programming interface (API). In an online store, for example, an OrderService may utilize a database that includes an ORDERS table. Similarly, a CustomerService may utilize a separate database, which includes a CUSTOMERS table. At development time, a developer can change a service's schema without having to coordinate with developers working on another service. At runtime, the services are isolated from each other. One benefit of such loose coupling is that a given microservice will never be blocked because another microservice holds a database lock, for example. Moreover, each microservice is usually considered to have its own domain model.
An unsolved problem inherent in such loose coupling of the microservices architecture is achieving consistency across the respective data layers associated with each microservice (i.e., insuring consistency of the relationships between data stored across different microservices). While each microservice may utilize its own database and data API, data stored across multiple microservices is typically related and thus the data stored in a data store for one microservice is correlated to data stored in a data store for another microservice. Typically, each microservice's persistent data is private to that service and accessible only via its data API. However, objects that are shared across the entirety of the microservices architecture, may reside in multiple microservices data stores. For example, objects may reside across multiple data stores associated with different microservices in a parent child relationship. Handling a situation in which one transaction succeeds on one or more microservices and fails on others creates significant potential for data inconsistencies and errors. In particular, users desire the emotional security of strict consistency. Thus, usable GUI applications should be designed in such a way as to provide the illusion of strict consistency even when the underlying APIs are eventually consistent. As an example, a user typically desires to know right away whether or not they were able to secure concert tickets when booking online because the next step is securing dinner reservation. If that person was not able to secure concert tickets, a confirmation that the funds were refunded to a credit card sometime between now and the next billing cycle would typically be required.
Although there exist methods such as two-phase commit and event sourcing to insure consistency of objects stored across data stores especially in the case if one or more microservices fail or some other system fault occurs (e.g., failure of an API gateway) in performing a transaction with respect to their data layer, they have associated disadvantages. In particular, two-phase commit requires that each microservice keeps transactions open across API calls. This approach is very fragile because the databases can become unresponsive quickly even when the microservices are working in only a slightly degraded state. Event sourcing, on the other hand, requires that all state mutating API calls to be eventually consistent. This approach increases the complexity of the GUI or application that calls these APIs.